app.js
import 'react-native-gesture-handler';
import React from 'react';
import { StatusBar } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import COLORS from './src/consts/colors';
import DetailsScreen from './src/views/screens/DetailsScreen';
import BottomNavigator from './src/views/navigation/BottomNavigator';
import OnBoardScreen from './src/views/screens/OnBoardScreen';
const Stack = createNativeStackNavigator();
const App = () => {
return (
<NavigationContainer>
<StatusBar backgroundColor={COLORS.white} barStyle="dark-content" />
<Stack.Navigator screenOptions={{ headerShown: false }}>
<Stack.Screen name="BoardScreen" component={OnBoardScreen} />
<Stack.Screen name="Home" component={BottomNavigator} />
<Stack.Screen name="DetailsScreen" component={DetailsScreen} />
</Stack.Navigator>
</NavigationContainer>
);
};
export default App;
src\views\screens\HomeScreen.js
import { SafeAreaView, StyleSheet, Text, View, Image, TextInput, ScrollView, TouchableOpacity, FlatList, Dimensions } from 'react-native'
import React, { useState } from 'react'
import Icon from 'react-native-vector-icons/MaterialIcons';
import COLORS from "../../consts/colors"
import categories from '../../consts/categories'
import foods from '../../consts/foods';
const { width } = Dimensions.get("screen")
const cardWidth = width / 2 - 20
const HomeScreen = ({ navigation }) => {
const [selectedCategoryIndex, setSelectedCategoryIndex] = useState(0)
// 項目清單
const ListCategories = () => {
return (
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={styles.CategoriesListContainer}
>
{/* 注意這邊的寫法 常用 但都記不住 XD */}
{categories.map((category, index) => (
<TouchableOpacity key={index}
activeOpacity={0.8}
// 點選後 給index的值 (與下面的判斷式配合使用)
onPress={() => setSelectedCategoryIndex(index)}
>
<View style={{
// 選取中的分類 與未選中的 背景色 判斷式
backgroundColor: selectedCategoryIndex == index
? COLORS.primary
: COLORS.secondary
, ...styles.CategoryBtn
}}>
<View style={styles.CategoryBtnImg}>
<Image source={category.image}
style={{ width: 35, height: 35, resizeMode: "cover" }}
/>
</View>
{/* 被選中的顏色改變 判斷式 */}
<Text style={{
fontSize: 15, fontWeight: "bold", marginLeft: 10,
color: selectedCategoryIndex == index
? COLORS.white
: COLORS.primary
}}>{category.name}</Text>
</View>
</TouchableOpacity>
))
}
</ScrollView >
)
}
// 食物的卡片
const Card = ({ food }) => {
return (
// navigation.navigate 這邊傳遞的 DetailsScreen 要先在App.js裡面設定
<TouchableOpacity underlayColor={COLORS.white} activeOpacity={0.9} onPress={() => navigation.navigate("DetailsScreen", food)}>
<View style={styles.Card}>
<View style={{ alignItems: "center", top: -40 }}>
<Image source={food.image}
style={{
width: 120,
height: 120,
}}
/>
</View>
<View style={{ marginHorizontal: 20 }}>
<Text style={{ fontSize: 17, fontWeight: "bold" }}>{food.name}</Text>
<Text style={{ fontSize: 14, color: COLORS.grey, marginTop: 2 }}>{food.ingredients}</Text>
</View>
<View style={{ flexDirection: "row", marginTop: 10, marginHorizontal: 20, justifyContent: "space-between" }}>
<Text style={{ fontSize: 18, fontWeight: "bold" }}>${food.price}</Text>
<View style={styles.AddToCartBtn}>
<Icon name="add" size={20} color={COLORS.white} />
</View>
</View>
</View>
</TouchableOpacity>
)
}
return (
<SafeAreaView style={{ flex: 1, backgroundColor: COLORS.white }}>
<View style={styles.Header}>
<View>
<View style={{ flexDirection: "row" }}>
<Text style={{ fontSize: 28, }}>Hello,</Text>
<Text style={{ fontSize: 28, fontWeight: "bold", marginLeft: 10 }}>Hsu</Text>
</View>
<Text style={{ marginTop: 5, fontSize: 22, color: COLORS.grey }}>What do ypu want today?</Text>
</View>
{/* 頭像 */}
<Image source={require("../../assets/person.png")}
style={{ width: 50, height: 50, borderRadius: 25 }}
/>
</View>
{/* 搜尋列 */}
<View style={{ marginTop: 40, flexDirection: "row", paddingHorizontal: 20 }}>
<View style={styles.InputContainer}>
<Icon name="search" size={28} />
<TextInput
placeholder='請輸入餐點名稱'
style={{ flex: 1, fontSize: 18, marginLeft: 5 }}
/>
</View>
{/* 排序按鈕 純展示 無作用 */}
<View style={styles.SortBtn}>
<Icon name="tune" size={28} color={COLORS.white} />
</View>
</View>
{/* 菜單項目的分類欄 */}
<View>
<ListCategories />
</View>
{/* 卡片 */}
<FlatList
showsVerticalScrollIndicator={false}
numColumns={2}
data={foods}
renderItem={({ item }) => <Card food={item} />}
/>
</SafeAreaView>
)
}
const styles = StyleSheet.create({
Header: {
marginTop: 20,
flexDirection: "row",
justifyContent: "space-between",
paddingHorizontal: 20,
},
InputContainer: {
flex: 1,
flexDirection: "row",
height: 50,
borderRadius: 10,
backgroundColor: COLORS.light,
alignItems: "center",
paddingHorizontal: 20,
}, SortBtn: {
width: 50,
height: 50,
marginLeft: 10,
backgroundColor: COLORS.primary,
borderRadius: 10,
justifyContent: "center",
alignItems: "center"
},
CategoriesListContainer: {
paddingVertical: 30,
paddingHorizontal: 20,
alignItems: "center",
},
CategoryBtn: {
flexDirection: "row",
width: 120,
height: 50,
marginRight: 7,
borderRadius: 30,
alignItems: "center",
paddingHorizontal: 5,
},
CategoryBtnImg: {
width: 35,
height: 35,
backgroundColor: COLORS.white,
borderRadius: 20,
justifyContent: "center",
alignItems: "center"
},
Card: {
width: cardWidth,
height: 220,
marginHorizontal: 10,
marginTop: 50,
marginBottom: 20,
borderRadius: 15,
elevation: 13,
backgroundColor: COLORS.white,
},
AddToCartBtn: {
width: 30,
height: 30,
borderRadius: 15,
backgroundColor: COLORS.primary,
justifyContent: "center",
alignItems: "center",
},
})
export default HomeScreen
src\views\screens\OnBoardScreen.js
import { StyleSheet, Text, View, Image, Button } from 'react-native';
import React from 'react';
import { SafeAreaView } from 'react-native-safe-area-context';
import COLORS from '../../consts/colors';
import { PrimaryButton } from '../components/Button';
// 登入畫面
const OnBoardScreen = ({ navigation }) => {
return (
<SafeAreaView style={{ flex: 1, backgroundColor: COLORS.white }}>
<View style={{ height: 400, }}>
<Image
source={require("../../assets/onboardImage.png")}
style={{ width: "100%", resizeMode: "contain", top: -180 }}
/>
</View>
<View style={styles.TextContainer}>
<View>
<Text style={{ fontSize: 32, fontWeight: "bold", textAlign: "center" }}>Delicious Food</Text>
<Text style={{
marginTop: 20,
fontSize: 18,
textAlign: "center",
color: COLORS.grey
}}> We help you to find best and delicious food</Text>
</View>
{/* indicator container */}
<View style={styles.IndicatorContainer}>
<View style={styles.CurrentIndicator}></View>
<View style={styles.Indicator}></View>
<View style={styles.Indicator}></View>
</View>
<PrimaryButton title="Get Started"
onPress={() => navigation.navigate("Home")}
/>
</View>
</SafeAreaView>
)
}
const styles = StyleSheet.create({
TextContainer: {
flex: 1,
paddingHorizontal: 50,
justifyContent: "space-between",
paddingBottom: 40,
},
IndicatorContainer: {
flex: 1,
height: 50,
justifyContent: "center",
flexDirection: "row",
alignItems: "center",
},
CurrentIndicator: {
width: 30,
height: 12,
borderRadius: 10,
backgroundColor: COLORS.primary,
marginHorizontal: 5,
},
Indicator: {
width: 12,
height: 12,
borderRadius: 6,
backgroundColor: COLORS.grey,
marginHorizontal: 5,
}
})
export default OnBoardScreen
src\views\navigation\BottomNavigator.js
import "react-native-gesture-handler"
import React from 'react'
import { createBottomTabNavigator } from "@react-navigation/bottom-tabs"
import Icon from 'react-native-vector-icons/MaterialIcons';
import COLORS from "../../consts/colors"
import { View } from "react-native"
import HomeScreen from '../screens/HomeScreen';
import CartScreen from '../screens/CartScreen';
const Tab = createBottomTabNavigator();
// tab 的 options 影片是舊版的 大部分已失效
// 原本是 tabBarOptions 改為 screenOptions
// 定義屬性的方式 也改變
// 參考官網文件 https://reactnavigation.org/docs/bottom-tab-navigator/#options
// 影片教學 Bottom Tab Navigation with Animation | React-Navigation v6/5 | Part-1 https://youtu.be/XiutL0uLICg
const BottomNavigator = () => {
return (
<Tab.Navigator
screenOptions={{
tabBarStyle: {
hight: 55,
//目前只有這個看得出有效果 上下兩個看不出來...
borderTopWidth: 0,
elevation: 1,
},
// 隱藏 標題列
headerShown: false,
tabBarActiveTintColor: COLORS.primary
}}>
{/* 頁籤1 HomeScreen */}
<Tab.Screen name="HomeScreen" component={HomeScreen} options={{ tabBarShowLabel: false, tabBarIcon: ({ color }) => (<Icon name="home" color={color} size={28} />) }} />
{/* 頁籤2 LocalMall */}
<Tab.Screen name="LocalMall" component={HomeScreen} options={{ tabBarShowLabel: false, tabBarIcon: ({ color }) => (<Icon name="local-mall" color={color} size={28} />) }} />
{/* 頁籤3 Search */}
<Tab.Screen name="Search" component={HomeScreen} options={{
tabBarShowLabel: false, tabBarIcon: ({ color }) => (
<View style={{
width: 60,
height: 60,
justifyContent: "center",
alignItems: "center",
backgroundColor: COLORS.white,
borderColor: COLORS.primary,
borderWidth: 2,
borderRadius: 30,
top: -25,
elevation: 5,
}}>
<Icon name="search" color={COLORS.primary} size={28} />
</View>
)
}} />
{/* 頁籤4 Favorite */}
<Tab.Screen name="Favorite" component={HomeScreen} options={{ tabBarShowLabel: false, tabBarIcon: ({ color }) => (<Icon name="favorite" color={color} size={28} />) }} />
{/* 頁籤5 Cart */}
<Tab.Screen name="Cart" component={CartScreen} options={{ tabBarShowLabel: false, tabBarIcon: ({ color }) => (<Icon name="shopping-cart" color={color} size={28} />) }} />
</Tab.Navigator>
)
}
export default BottomNavigator
src\views\components\Button.js
import { StyleSheet, View, Text, TouchableOpacity } from 'react-native'
import React from 'react'
import COLORS from '../../consts/colors'
const PrimaryButton = ({ title, onPress = () => { } }) => {
return (
<TouchableOpacity
activeOpacity={0.8}
onPress={onPress}
>
<View style={styles.BtnContainer}>
<Text style={styles.Title}>{title}</Text>
</View>
</TouchableOpacity>
)
}
const OrderButton = ({ title, onPress = () => { } }) => {
return (
<TouchableOpacity
activeOpacity={0.8}
onPress={onPress}
>
{/* 這邊注意 套用 多重樣式的寫法 */}
<View style={{ ...styles.BtnContainer, backgroundColor: COLORS.white }}>
<Text style={{ ...styles.Title, color: COLORS.primary }}>{title}</Text>
</View>
</TouchableOpacity>
)
}
const styles = StyleSheet.create({
Title: { color: COLORS.white, fontWeight: 'bold', fontSize: 18 },
BtnContainer: {
backgroundColor: COLORS.primary,
height: 60,
borderRadius: 30,
justifyContent: "center",
alignItems: "center",
},
})
// export default Button
//注意這邊 的匯出方式
export { PrimaryButton, OrderButton }
src\views\screens\CartScreen.js
import { StyleSheet, Text, View, SafeAreaView, ScrollView, Image, FlatList } from 'react-native'
import React from 'react'
import Icon from 'react-native-vector-icons/MaterialIcons';
import COLORS from "../../consts/colors"
import { PrimaryButton } from '../components/Button';
import foods from '../../consts/foods';
const CartScreen = ({ navigation }) => {
// 購物車資料
const CartCard = ({ item }) => {
return (
<View style={styles.CartCard}>
{/* 圖片 */}
<Image source={item.image}
style={{ width: 80, height: 80 }}
/>
{/* 名稱跟價錢 */}
<View style={{ flex: 1, height: 100, marginLeft: 10, paddingVertical: 20 }}>
<Text style={{ fontSize: 16, fontWeight: "bold" }}>{item.name}</Text>
<Text style={{ fontSize: 13, color: COLORS.grey }}>{item.ingredients}</Text>
<Text style={{ fontSize: 18, fontWeight: "bold" }}>${item.price}</Text>
</View>
{/* 訂購數量跟按鈕 */}
<View style={{ marginLeft: 20, alignItems: "center" }}>
<Text style={{ fontSize: 18, fontWeight: "bold" }}>3</Text>
<View style={styles.ActionBtn}>
<Icon name="remove" size={25} color={COLORS.white} />
<Icon name="add" size={25} color={COLORS.white} />
</View>
</View>
</View>
)
}
return (
<SafeAreaView style={{ flex: 1, backgroundColor: COLORS.white }}>
<View style={styles.Header}>
<Icon name="arrow-back-ios" size={28} onPress={navigation.goBack} />
<Text style={{ fontSize: 20, fontWeight: "bold" }}>Cart</Text>
</View>
{/* 購物車清單 */}
<FlatList showsVerticalScrollIndicator={false}
contentContainerStyle={{ paddingBottom: 80 }}
data={foods}
renderItem={({ item }) => <CartCard item={item} />}
ListFooterComponentStyle={{ paddingHorizontal: 20, marginTop: 20 }}
ListFooterComponent={() => (
<View>
<View style={{ flexDirection: "row", justifyContent: "space-between", marginVertical: 15 }}>
<Text style={{ fontSize: 18, fontWeight: "bold" }}>Total Price</Text>
<Text style={{ fontSize: 18, fontWeight: "bold" }}>$50</Text>
</View>
<View style={{ marginTop: 30 }}>
<PrimaryButton title="CHECKOUT" />
</View>
</View>
)}
/>
</SafeAreaView >
)
}
{/* <PrimaryButton title="Get Started"
onPress={() => navigation.navigate("Home")}
/> */}
const styles = StyleSheet.create({
Header: {
flexDirection: "row",
paddingHorizontal: 20,
alignItems: "center",
marginHorizontal: 20,
},
CartCard: {
flexDirection: "row",
height: 100,
elevation: 15,
borderRadius: 10,
backgroundColor: COLORS.white,
marginVertical: 10,
marginHorizontal: 20,
paddingHorizontal: 10,
alignItems: "center",
},
ActionBtn: {
flexDirection: "row",
width: 80,
height: 30,
backgroundColor: COLORS.primary,
borderRadius: 30,
paddingHorizontal: 5,
justifyContent: "space-between",
alignItems: "center"
},
})
export default CartScreen
const categories = [
{id: '1', name: 'pizza', image: require('../assets/catergories/pizza.png')},
{id: '2', name: 'Burger', image: require('../assets/catergories/burger.png')},
{id: '3', name: 'Sushi', image: require('../assets/catergories/sushi.png')},
{id: '4', name: 'Salad', image: require('../assets/catergories/salad.png')},
];
export default categories;
src\views\screens\DetailsScreen.js
import { StyleSheet, Text, View, SafeAreaView, ScrollView, Image } from 'react-native'
import React from 'react'
import Icon from 'react-native-vector-icons/MaterialIcons';
import COLORS from "../../consts/colors"
import { OrderButton } from '../components/Button';
const DetailsScreen = ({ navigation, route }) => {
// 設定 item變數 來接收 上一頁傳來的參數
const item = route.params;
// 印出 上一頁傳來的參數 測試用
// console.log(item)
return (
<SafeAreaView style={{ backgroundColor: COLORS.white }}>
<View style={styles.Header}>
<Icon name="arrow-back-ios" size={28} onPress={() => navigation.goBack()} />
<Text style={{ fontSize: 20, fontWeight: "bold" }}>Details</Text>
</View>
<ScrollView showsVerticalScrollIndicator={false}>
{/* 食物大圖 */}
<View style={{ justifyContent: "center", alignItems: "center", height: 280 }}>
<Image source={item.image} style={{ width: 220, height: 220, }} />
</View>
{/* 食物簡介 */}
<View style={styles.Details}>
{/* 標題 */}
<View style={{ flexDirection: "row", justifyContent: "space-between", alignItems: "center" }}>
<Text style={{ fontSize: 20, fontWeight: "bold", color: COLORS.white }}>{item.name}</Text>
<View style={styles.IconContainer}>
<Icon name="favorite-border" size={24} style={{ color: COLORS.primary }} />
</View>
</View>
{/* 文字區塊 */}
<Text style={styles.DetailsText}>Lorem ipsum dolor, sit amet consectetur adipisicing elit. Veritatis id labore architecto eius necessitatibus hic beatae, esse quia provident doloribus. Minima nesciunt a sint eaque dolor illo blanditiis aut corporis.</Text>
{/* 訂購按鈕 */}
<View style={{ marginTop: 40, marginBottom: 40 }}>
<OrderButton title="Add to Cart" />
</View>
</View>
</ScrollView>
</SafeAreaView >
)
}
export default DetailsScreen
const styles = StyleSheet.create({
Header: {
flexDirection: "row",
paddingHorizontal: 20,
alignItems: "center",
marginHorizontal: 20,
},
Details: {
paddingHorizontal: 20,
paddingTop: 40,
paddingBottom: 60,
backgroundColor: COLORS.primary,
borderTopLeftRadius: 40,
borderTopRightRadius: 40,
},
IconContainer: {
width: 50,
height: 50,
borderRadius: 25,
backgroundColor: COLORS.white,
justifyContent: "center",
alignItems: "center"
},
DetailsText: {
marginTop: 10,
lineHeight: 22,
fontSize: 16,
color: COLORS.white
}
})
src\consts\foods.js
const foods = [
{
id: '1',
name: 'Meat Pizza',
ingredients: 'Mixed Pizza',
price: '8.30',
image: require('../assets/meatPizza.png'),
},
{
id: '2',
name: 'Cheese Pizza',
ingredients: 'Cheese Pizza',
price: '7.10',
image: require('../assets/cheesePizza.png'),
},
{
id: '3',
name: 'Chicken Burger',
ingredients: 'Fried Chicken',
price: '5.10',
image: require('../assets/chickenBurger.png'),
},
{
id: '4',
name: 'Sushi Makizushi',
ingredients: 'Salmon Meat',
price: '9.55',
image: require('../assets/sushiMakizushi.png'),
},
];
export default foods;
src\consts\colors.js
const COLORS = {
white: '#FFF',
dark: '#000',
primary: '#F9813A',
secondary: '#fedac5',
light: '#E5E5E5',
grey: '#908e8c',
};
git clone https://smilehsu@bitbucket.org/smilehsu/yt_example_foodui0218.git